﻿
#region using declarations

using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;
using Mitosoft.Definitions.General;

#endregion

namespace Mitosoft.Definitions.Controls
{
  /// <summary>
  /// Enhanced TreeView control enabling TreeNode
  /// dragging and some other things.
  /// </summary>
  public class TreeViewControl : TreeView
  {
    #region Delegates and Events

    /// <summary>
    /// Method called when <see cref="TreeNodeWasDragged"/> occured.
    /// </summary>
    /// <param name="draggedTreeNode">Represents the tree node which was dragged.</param>
    /// <param name="targetTreeNode">Represents the tree node where the another tree node
    /// was dragged to.</param>
    public delegate void TreeNodeWasDraggedDelegate(TreeNode draggedTreeNode, TreeNode targetTreeNode);

    /// <summary>
    /// Occurs when a tree node was dragged onto another tree node.
    /// </summary>
    public event TreeNodeWasDraggedDelegate TreeNodeWasDragged;

    #endregion

    #region Fields

    protected object _rememberedTagObject;
    protected bool _isTreeNodeDragging;
    protected TreeNode _dragStartingTreeNode;

    #endregion

    #region Constructor(s)

    /// <summary>
    /// Creates an instance of 
    /// </summary>
    public TreeViewControl()
    {
      this.MouseDown += this.TreeViewControl_MouseDown;
      this.MouseUp += TreeViewControl_MouseUp;
    }

    #endregion

    #region Properties

    /// <summary>
    /// Gets or sets the tree node beeing dragged
    /// on to the target tree node.
    /// </summary>
    [Browsable(false)]
    protected TreeNode DragStartingTreeNode
    {
      get { return _dragStartingTreeNode; }
      set { _dragStartingTreeNode = value; }
    }

    /// <summary>
    /// Gets or sets whether a tree node is currently
    /// beeing dragged.
    /// </summary>
    [Browsable(false)]
    protected bool IsTreeNodeDragging
    {
      get { return _isTreeNodeDragging; }
      set { _isTreeNodeDragging = value; }
    }

    /// <summary>
    /// Represents the object contained in the
    /// Tag property of a node.
    /// </summary>
    [Browsable(false)]
    protected object RememberedTagObject
    {
      get { return _rememberedTagObject; }
      set { _rememberedTagObject = value; }
    }

    /// <summary>
    /// Gets the selected node name.
    /// </summary>
    [Browsable(false)]
    public string SelectedNodeName
    {
      get
      {
        return
          this.SelectedNode == null ?
            null : 
            this.SelectedNode.Name;
      }
    }

    /// <summary>
    /// Gets the collection of all tree nodes.
    /// Usefull for example to avoid extra
    /// recursively programming.
    /// </summary>
    [Browsable(false)]
    protected List<TreeNode> AllTreeNodes
    {
      get
      {
        if (this.Nodes == null ||
            this.Nodes.Count <= 0)
          return null;

        List<TreeNode> foundNodes = new List<TreeNode>();
        foreach (TreeNode treeNode in this.Nodes)
          this.GetTreeNodes(treeNode, foundNodes);

        return 
          foundNodes.Count <= 0 ? 
            null : foundNodes;
      }
    }

    #endregion

    #region Methods

    /// <summary>
    /// Gets recursively all tree nodes starting at the
    /// given tree node and stores the found tree nodes
    /// in the given collection.
    /// </summary>
    /// <param name="currentTreeNode">Tree node which will be
    /// parsed recursively. The given tree node will also be
    /// contained in the result collection.</param>
    /// <param name="result">Array where the found tree nodes
    /// will be stored.</param>
    protected void GetTreeNodes(TreeNode currentTreeNode, List<TreeNode> result)
    {
      if (currentTreeNode == null) return;
      
      result.Add(currentTreeNode);
      
      if (currentTreeNode.Nodes == null || 
          currentTreeNode.Nodes.Count <= 0)
        return;

      foreach (TreeNode treeNode in currentTreeNode.Nodes)
        this.GetTreeNodes(treeNode, result);
    }

    /// <summary>
    /// Stores the Tag property object of the currently
    /// selected node.
    /// </summary>
    public void RememberSelectedNodeByTag()
    {
      this.RememberedTagObject = 
        this.SelectedNode == null ? null : this.SelectedNode.Tag;
    }

    /// <summary>
    /// Sets the selected node where the node will be identified
    /// by the tag object property stored by calling 
    /// RememberSelectedNodeByTag.
    /// </summary>
    public void RestoreSelectedNodeByTag()
    {
      if (this.RememberedTagObject == null) return;

      // Search for the node containing the same object as
      // this.RememberedTagObject:
      List<TreeNode> allTreeNodes = this.AllTreeNodes;

      if (allTreeNodes == null) return;

      foreach (TreeNode treeNode in allTreeNodes)
      {
        if (treeNode.Tag == null || treeNode.Tag != this.RememberedTagObject) continue;
        this.SelectedNode = treeNode;
        return;
      }

    }

    /// <summary>
    /// Sets the node identified by the tag object
    /// beeing IGuidIdentifiable with the given Guid.
    /// </summary>
    /// <param name="guid"></param>
    public void SelectTreeNode(System.Guid guid)
    {
      if (guid == System.Guid.Empty) return;

      List<TreeNode> allTreeNodes = this.AllTreeNodes;
      if (allTreeNodes == null || allTreeNodes.Count <= 0) return;

      foreach (TreeNode treeNode in allTreeNodes)
      {
        if (treeNode.Tag == null 
            || !(treeNode.Tag is IGuidIdentifiable) 
            || ((IGuidIdentifiable) treeNode.Tag).Guid != guid) 
          continue;
        
        this.SelectedNode = treeNode;
        this.SelectedNode.EnsureVisible();
        return;
      }
    }

    /// <summary>
    /// Occurs when the mouse is clicked down.
    /// </summary>
    /// <param name="sender">Not used.</param>
    /// <param name="e">Not used.</param>
    protected void TreeViewControl_MouseDown(object sender, MouseEventArgs e)
    {
      if (this.SelectedNode == null || 
          this.GetNodeAt(this.PointToClient(MousePosition)) != this.SelectedNode) 
        return;
      
      this.IsTreeNodeDragging = true;
      this.DragStartingTreeNode = this.SelectedNode;
    }

    /// <summary>
    /// Occurs when the mouse is released.
    /// </summary>
    /// <param name="sender">Not used.</param>
    /// <param name="e">Not used.</param>
    protected void TreeViewControl_MouseUp(object sender, MouseEventArgs e)
    {
      if (!this.IsTreeNodeDragging) return;

      if (this.TreeNodeWasDragged == null) return;

      this.IsTreeNodeDragging = false;

      TreeNode dragEndingTreeNode = this.GetNodeAt(this.PointToClient(MousePosition));

      if (dragEndingTreeNode == null || dragEndingTreeNode == this.DragStartingTreeNode) return;

      // Throw event:
      this.TreeNodeWasDragged(this.DragStartingTreeNode, dragEndingTreeNode);
    }

    #endregion
  }
}
